﻿using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using BasicParts;

namespace ParticleMachine
{
    public class Emitter
    {
        #region Declarations & Properties
        public bool active;
        protected Vector2 position;
        protected string id;

        protected List<Particle> members;

        //public Texture2D texture;
        protected IEmittable sprite;
        protected string spriteID;
        //private Animation animation;
        protected Vector2 velocity;
        protected int maxLifeTime;

        //Alternatives to a Vector2 velocity.
        //Remove ignore attributes if we use this.
        protected float vel;
        protected float dir;

        protected int addFrequency;
        protected int addTimer;
        protected int addIndex;

        public Rectangle activeArea;
        public bool inActiveArea;

        //Randomness variables.
        private System.Random random;
        private float randomAdd;
        private float randomLifeTime;
        private float randomVelocity;
        private float randomDirection;

        public List<Particle> Members { get { return members; } }

        public Vector2 Position
        {
            get
            {
                return position;
            }
            set
            {
                position = value;
                CalcActiveArea();
            }
        }
        #endregion //Declarations & Properties

        #region Constructors
        public Emitter()
        {
            random = new System.Random();
            randomAdd = randomDirection = randomLifeTime = randomVelocity = 0;
            randomAdd = 0.5f;
            addIndex = 0;
            addTimer = 0;
            velocity = Vector2.Zero;
        }

        public Emitter(int addFrequency, int maxLifeTime,
            Vector2 velocity, IEmittable sprite, Vector2 startPosition)
        {
            addIndex = 0;
            addTimer = 0;
            this.addFrequency = addFrequency;
            this.maxLifeTime = maxLifeTime;
            this.velocity = velocity;
            this.sprite = sprite;
            spriteID = sprite.Id;
            this.position = startPosition;

            CalcActiveArea();
            InitMembers();
        }
        #endregion //Constructors

        #region Value Setting Methods
        /// <summary>
        /// Resets the animation and sets the spriteID.
        /// Needs to be called whenever the sprite reference has been altered.
        /// </summary>
        public void ResetAnimation()
        {
            spriteID = sprite.Id;
            members.ForEach(m => m.frameIndex = m.frameTimer = 0);
        }

        /// <summary>
        /// Initializes a fix number of members, based on how many can possibly
        /// be expected to appear simultaneously.
        /// Needs to be called whenever maxLifeTime or addFrequency have been altered.
        /// </summary>
        public void InitMembers()
        {
            addIndex = 0;
            int maxMembers = (addFrequency <= 0 || addFrequency > maxLifeTime)
                ? 1 : (int)(maxLifeTime / addFrequency) + 1; //simple way of rounding up.
            members = new List<Particle>(maxMembers);
            for (int k = 0; k < maxMembers; k++)
                members.Add(new Particle());
        }

        /// <summary>
        /// Calculate the active area; i.e. a rectangle covering where the particles can possibly be.
        /// Needs to be called whenever velocity, maxLifeTime or position have been altered.
        /// </summary>
        public void CalcActiveArea()
        {
            float vX = velocity.X < 0 ? -velocity.X : velocity.X;
            float vY = velocity.Y < 0 ? -velocity.Y : velocity.Y;
            activeArea = new Rectangle(
                velocity.X >= 0 ? (int)position.X : (int)(position.X - vX * maxLifeTime),
                velocity.Y >= 0 ? (int)position.Y : (int)(position.Y - vY * maxLifeTime),
                (int)(vX * maxLifeTime), (int)(vY * maxLifeTime));
            //Increase the activeArea on all sides to take the sprite's size into consideration.
            activeArea.X -= sprite.FrameRectangle.Width;
            activeArea.Y -= sprite.FrameRectangle.Height;
            activeArea.Width += 2 * sprite.FrameRectangle.Width;
            activeArea.Height += 2 * sprite.FrameRectangle.Height;
        }
        #endregion //Value Setting Methods

        #region Update
        /// <summary>
        /// Updates all particles and adds new ones.
        /// </summary>
        /// <param name="elapsedTime"></param>
        public void Update(int elapsedTime)
        {
            //int lifeAdd = elapsedTime +randomLifeTime * r;
            //int velAdd = velocity * elapsedTime +randomVelocity * r;
            //Update all particles.
            foreach (Particle ps in members.Where(m => m.alive))
            {
                ps.lifeTime += elapsedTime;
                if (ps.lifeTime >= maxLifeTime)
                    ps.alive = false;
                else
                {
                    ps.position += velocity * elapsedTime;
                    if (sprite.Animated)
                    {
                        ps.frameTimer += elapsedTime;
                        if (ps.frameTimer >= sprite.FrameDelay)
                        {
                            ps.frameTimer = 0;
                            ps.frameIndex = ps.frameIndex < sprite.NumOfFrames - 1
                                ? ps.frameIndex + 1 : 0;
                        }
                    }
                }
            }

            //Add new particles?
            addTimer += elapsedTime;
            if (addTimer >= addFrequency)
            {
                //addTimer = (int)((random.Next(addFrequency) - addFrequency * 0.5f) * randomAdd);
                addTimer = 0;
                members[addIndex].alive = true;
                members[addIndex].position = position;
                members[addIndex].lifeTime = 0;
                addIndex = addIndex < members.Count - 1 ? addIndex + 1 : 0;
            }
        }

        /// <summary>
        /// Spawns particles along the entire line.
        /// </summary>
        public void SpawnInitialParticles()
        {
            //Find the distance between each particle.
            Vector2 distance = addFrequency * velocity;
            for (int k = 0; k < members.Capacity; k++)
            {
                members[k].alive = true;
                members[k].position = position + distance * k;
                members[k].lifeTime = members[k].frameTimer = addFrequency * k;
                if (sprite.Animated)
                {
                    members[k].frameIndex = (members[k].frameTimer / sprite.FrameDelay)
                        % sprite.NumOfFrames;
                    members[k].frameTimer %= sprite.FrameDelay;
                }
            }
            addIndex = members.Capacity - 1;
        }
        #endregion //Update
    }
}